feat: Added 'FOR UPDATE SKIP LOCKED' to SelectQuery#243
Conversation
There was a problem hiding this comment.
I think it's time to increase min PHP version to 8.1 and use enums.
/**
* Row-level lock strength.
* PG-specific modes fallback to Update/Share on other drivers.
*/
enum LockMode
{
/** Exclusive lock. Blocks UPDATE, DELETE, SELECT FOR UPDATE/SHARE. */
case Update;
/** Shared lock. Blocks UPDATE, DELETE, SELECT FOR UPDATE. Allows FOR SHARE. */
case Share;
/**
* PG only. Like Update, but doesn't block FOR KEY SHARE.
* Use when not modifying PK/FK columns.
* Fallback: Update (MySQL, MSSQL), noop (SQLite).
*/
case NoKeyUpdate;
/**
* PG only. Weakest lock — blocks only DELETE and PK/FK updates.
* Fallback: Share (MySQL, MSSQL), noop (SQLite).
*/
case KeyShare;
}
/**
* Lock wait behavior when row is already locked.
*/
enum LockBehavior
{
/** Default. Block until lock is released. */
case Wait;
/**
* Fail immediately if row is locked.
* PG: NOWAIT, MySQL: NOWAIT, MSSQL: SET LOCK_TIMEOUT 0.
* SQLite: NotSupportedException.
*/
case NoWait;
/**
* Skip locked rows, return only unlocked. Useful for job queues.
* PG: SKIP LOCKED, MySQL: SKIP LOCKED (8.0+), MSSQL: READPAST hint.
* SQLite: NotSupportedException.
*/
case SkipLocked;
}
interface SelectQuery
{
// ...
/**
* Add row-level locking clause.
*
* @param LockMode $mode Lock strength. SQLite: ignored, uses BEGIN IMMEDIATE.
* @param LockBehavior $behavior Wait strategy. SQLite: only Wait supported.
*/
public function forUpdate(
LockMode $mode = LockMode::Update,
LockBehavior $behavior = LockBehavior::Wait,
): self;
}
// Usage
$query->forUpdate(LockMode::Update, LockBehavior::SkipLocked);Feel free to use enums in this PR, the PHP version must be bumped separately.
ca3dbb6 to
1cfa7f8
Compare
|
Ready. Maybe we should add method lock($mode, $behavior) and deprecate forUpdate()? And rename field $forUpdate to $lock? |
|
I think this API will be better: |
|
How should the values LockMode::KeyShare and Lock::NoKeyUpdate be passed then? |
|
->forUpdate(LockBehavior::NoWait, noKey: true) // PG only, exception for others
->forShare(keyOnly: true) // PG only |
1cfa7f8 to
3a6b51a
Compare
|
Yes, that's better. Ready. |
3a6b51a to
30da851
Compare
|
Now tests passed :) |
There was a problem hiding this comment.
Pull request overview
This PR adds row-lock mode and wait behavior support to SelectQuery, including dialect-specific SQL for FOR UPDATE, FOR SHARE, NOWAIT, and SKIP LOCKED, addressing issue #240.
Changes:
- Introduces
LockModeandLockBehaviorenums and updatesSelectQuerylock state. - Adds compiler support for PostgreSQL/default, MySQL, SQL Server, and SQLite behavior.
- Expands functional/unit tests for lock modes and behaviors across drivers.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
src/Query/SelectQuery.php |
Adds forShare() and enum-based forUpdate() state. |
src/Query/Enum/LockMode.php |
Defines row-level lock strength enum. |
src/Query/Enum/LockBehavior.php |
Defines lock wait behavior enum. |
src/Driver/Compiler.php |
Compiles generic/PostgreSQL-style lock clauses. |
src/Driver/CompilerCache.php |
Includes lock mode/behavior in select cache hashes. |
src/Driver/MySQL/MySQLCompiler.php |
Compiles MySQL lock clauses and behaviors. |
src/Driver/Postgres/Query/PostgresSelectQuery.php |
Adds PostgreSQL-specific no-key/key-share lock options. |
src/Driver/SQLServer/SQLServerCompiler.php |
Compiles SQL Server table hints for lock modes/behaviors. |
src/Driver/SQLite/SQLiteCompiler.php |
Continues ignoring unsupported lock clauses with new token shape. |
tests/Database/Functional/Driver/Common/Query/SelectQueryTest.php |
Adds common lock mode/behavior SQL expectations. |
tests/Database/Functional/Driver/Postgres/Query/SelectQueryTest.php |
Adds PostgreSQL-specific lock mode tests. |
tests/Database/Functional/Driver/SQLServer/Query/SelectQueryTest.php |
Adds SQL Server-specific lock behavior tests. |
tests/Database/Functional/Driver/SQLite/Query/SelectQueryTest.php |
Adds SQLite lock no-op expectations. |
tests/Database/Unit/Query/Tokens/SelectQueryTest.php |
Updates token assertions for enum-based lock state. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| break; | ||
| case LockMode::Update: | ||
| case LockMode::NoKeyUpdate: | ||
| $arguments[] = 'UPDLOCK'; |
| namespace Cycle\Database\Tests\Functional\Driver\SQLite\Query; | ||
|
|
||
| // phpcs:ignore | ||
| use Cycle\Database\Query\Enum\LockMode; |
| $this->assertSameQuery("SELECT * FROM {table} ORDER BY {logs}->>'created_at' DESC", $select); | ||
| } | ||
|
|
||
| public function testSelectForUpdateLockModeUpdate(): void |
| ); | ||
| } | ||
|
|
||
| public function testSelectForUpdateLockModeShare(): void |
|
|
||
|
|
| @@ -0,0 +1,56 @@ | |||
| <?php | |||
|
|
|||
| @@ -0,0 +1,44 @@ | |||
| <?php | |||
|
|
|||
| * Like LockMode::Update, but doesn't block PK/FK columns. | ||
| * (Use when not modifying PK/FK columns) |
🤔 Why?
Issues240